Concepte fundamentale ale limbajelor de programare

2. Tipuri și membri în C#

Ca limbaj orientat pe obiecte, C# acceptă conceptele de încapsulare, moștenire și polimorfism. O clasă poate moșteni direct de la o clasă părinte și poate implementa orice număr de interfețe. Metodele care suprascriu metodele virtuale dintr-o clasă părinte necesită cuvântul cheie override ca o modalitate de a evita redefinirea accidentală. În C#, o structură este ca o clasă simplificată; este un tip alocat în stivă care poate implementa interfețe, dar nu acceptă moștenirea. C# oferă clase de tip record și structuri de tip record, care sunt tipuri al căror scop este în primul rând stocarea valorilor datelor.

2.1 Clase și obiecte

Clasele sunt cele mai importante dintre tipurile C#. O clasă este o structură de date care combină stările (câmpuri) și acțiunile (metode și alți membri ai funcției) într-o singură unitate. O clasă oferă o definiție pentru instanțe ale clasei, cunoscute și ca obiecte. Clasele suportă moștenirea și polimorfismul, mecanisme prin care clasele derivate pot extinde și specializa clasele de bază.

Clasele noi sunt create folosind declarații de clasă. O declarație de clasă începe cu un antet. Antetul specifică:
Atributele și modificatorii clasei
Numele clasei
Clasa de bază (când moșteniți de la o clasă de bază)
Interfețele implementate de clasă.
Antetul este urmat de corpul clasei, care constă dintr-o listă de declarații de membri scrise între delimitatorii { and }.
Următorul cod arată o declarație a unei clase simple numite Point:

public class Point
{
    public int X { get; }
    public int Y { get; }

    public Point(int x, int y) => (X, Y) = (x, y);
}

Instanțele claselor sunt create folosind operatorul new, care alocă memorie pentru o instanță nouă, invocă un constructor pentru a inițializa instanța și returnează o referință la instanță. Următoarele instrucțiuni creează două obiecte Point și stochează referințe la acele obiecte în două variabile:

var p1 = new Point(0, 0);
var p2 = new Point(10, 20);

Memoria ocupată de un obiect este recuperată automat atunci când obiectul nu mai este accesibil. Nu este necesar sau posibil să dezalocați în mod explicit obiectele în C#.

2.2 Parametrii type

Clasele generice definesc parametrii type. Parametrii type sunt o listă de nume de parametri de tip cuprinse între paranteze unghiulare. Parametrii type urmează numele clasei. Parametrii type pot fi apoi utilizați în corpul declarațiilor de clasă pentru a defini membrii clasei. În exemplul următor, parametrii type ai clasei Pair sunt TFirst și TSecond:

public class Pair
{
    public TFirst First { get; }
    public TSecond Second { get; }
    
    public Pair(TFirst first, TSecond second) => 
        (First, Second) = (first, second);
}

O clasă type care este declarată să accepte parametrii type se numește tip de clasă generică. Type-urile de structură, interfață și delegat pot fi, de asemenea, generice. Când se utilizează clasa generică, trebuie furnizate argumente type pentru fiecare dintre parametrii type:

var pair = new Pair(1, "two");
int i = pair.First;     //TFirst int
string s = pair.Second; //TSecond string

Un type generic cu argumente type furnizate, cum ar fi Pair de mai sus, se numește constructed type.

2.3 Clase de bază

O declarație de clasă poate specifica o clasă de bază. Urmați numele clasei și parametrii type cu două puncte și numele clasei de bază. Omiterea unei specificații a clasei de bază este aceeași cu derivarea din tipul obiectului. În exemplul următor, clasa de bază a Point3D este Point. Din primul exemplu, clasa de bază a Point este obiect:

public class Point3D : Point
{
    public int Z { get; set; }
    
    public Point3D(int x, int y, int z) : base(x, y)
    {
        Z = z;
    }
}

O clasă moștenește membrii clasei sale de bază. Moștenirea înseamnă că o clasă conține implicit aproape toți membrii clasei sale de bază. O clasă nu moștenește instanța și constructorii statici și finalizatorul. O clasă derivată poate adăuga noi membri acelor membri pe care îi moștenește, dar nu poate elimina definiția unui membru moștenit. În exemplul anterior, Point3D moștenește membrii X și Y de la Point și fiecare instanță Point3D conține trei proprietăți, X, Y și Z.

Există o conversie implicită de la type de clasă la oricare dintre type-urile sale de clasă de bază. O variabilă a unui type de clasă poate face referire la o instanță a acelei clase sau la o instanță a oricărei clase derivate. De exemplu, având în vedere declarațiile anterioare de clasă, o variabilă type Point poate face referire fie la un Point, fie la un Point3D:

    Point a = new(10, 20);
    Point b = new Point3D(10, 20, 30);

2.4 Structuri

Clasele definesc type-uri care suportă moștenirea și polimorfismul. Acestea vă permit să creați comportamente sofisticate bazate pe ierarhii de clase derivate. Prin contrast, type-urile struct sunt type-uri mai simple al căror scop principal este stocarea valorilor datelor. Structurile nu pot declara un type de bază; ele derivă implicit din System.ValueType. Nu puteți deriva alte type-uri de struct dintr-un type struct. Sunt implicit sigilate.
public struct Point
{
    public double X { get; }
    public double Y { get; }
    
    public Point(double x, double y) => (X, Y) = (x, y);
}

Interfețe

O interfață definește un contract care poate fi implementat prin clase și structuri. Definim o interfață pentru a declara capabilitățile care sunt partajate între type-uri distincte. De exemplu, interfața System.Collections.Generic.IEnumerable definește o modalitate consistentă de a parcurge toate elementele dintr-o colecție, cum ar fi o matrice. O interfață poate conține metode, proprietăți, evenimente și indexuri. De obicei, o interfață nu oferă implementări ale membrilor pe care îi definește - doar specifică membrii care trebuie furnizați de clase sau structuri care implementează interfața.

Interfețele pot folosi moșteniri multiple. În exemplul următor, interfața IComboBox moștenește atât de la ITextBox, cât și de la IListBox.

interface IControl
{
    void Paint();
}

interface ITextBox : IControl
{
    void SetText(string text);
}

interface IListBox : IControl
{
    void SetItems(string[] items);
}

interface IComboBox : ITextBox, IListBox { }

Clasele și structurile pot implementa mai multe interfețe. În exemplul următor, clasa EditBox implementează atât IControl, cât și IDataBound.

interface IDataBound
{
    void Bind(Binder b);
}

public class EditBox : IControl, IDataBound
{
    public void Paint() { }
    public void Bind(Binder b) { }
}

Când o clasă sau o structură implementează o anumită interfață, instanțe ale acelei clase sau structuri pot fi implicit convertite la acel type de interfață. De exemplu:

EditBox editBox = new();
IControl control = editBox;
IDataBound dataBound = editBox;

2.5 Enumerări

Un tip Enum definește un set de valori constante. Următoarea enumerare declară constante care definesc diferite legume rădăcinoase:

public enum SomeRootVegetable
{
    HorseRadish,
    Radish,
    Turnip
}

De asemenea, puteți defini o enumerare care să fie utilizată în combinație ca flaguri. Următoarea declarație declară un set de flaguri pentru cele patru sezoane de anotimpuri. Se poate aplica orice combinație de anotimpuri, inclusiv o valoare All care include toate anotimpurile:

[Flags]
public enum Seasons
{
    None = 0,
    Summer = 1,
    Autumn = 2,
    Winter = 4,
    Spring = 8,
    All = Summer | Autumn | Winter | Spring
}
Următorul exemplu arată declarațiile ambelor enumerări precedente:
var turnip = SomeRootVegetable.Turnip;

var spring = Seasons.Spring;
var startingOnEquinox = Seasons.Spring | Seasons.Autumn;
var theYear = Seasons.All;

2.6 Tipuri nullable

Variabilele de orice tip pot fi declarate ca non-nullable sau nullable. O variabilă nulă poate deține o valoare nulă suplimentară, indicând nicio valoare. Tipurile de valori nullabile (structuri sau enumerari) sunt reprezentate de System.Nullable. Tipurile de referință non-nullable și nullable sunt ambele reprezentate de tipul de referință la bază. Distincția este reprezentată de metadate citite de compilator și de unele biblioteci. Compilatorul oferă avertismente atunci când referințele nullable sunt dereferențiate fără a verifica mai întâi valoarea lor față de null. Compilatorul oferă, de asemenea, avertismente atunci când referințelor care nu pot fi anulate li se atribuie o valoare care poate fi nulă. Următorul exemplu declară un int nullable, inițialându-l la null. Apoi, setează valoarea la 5. Demonstrăm același concept cu un șir nullable. Pentru mai multe informații, consultați tipuri de valori nullabile și tipuri de referințe nullable.

int? optionalInt = default; 
optionalInt = 5;
string? optionalText = default;
optionalText = "Hello World.";

2.7 Tupluri

C# acceptă tupluri, care oferă o sintaxă concisă pentru a grupa mai multe elemente de date într-o structură de date ușoară. Instanțiem un tuplu declarând tipurile și numele membrilor între (and), așa cum se arată în exemplul următor:

(double Sum, int Count) t2 = (4.5, 3);
Console.WriteLine($"Sum of {t2.Count} elements is {t2.Sum}.");
//Output:
//Sum of 3 elements is 4.5.

Tuplurile oferă o alternativă pentru structura de date cu mai mulți membri, fără a utiliza blocurile descrise în articolul următor.

2.8 Sarcini

TODO

Bibliografie

[1] Microsoft Corporation. C# Documentation, https://docs.microsoft.com/en-us/dotnet/csharp/, 2022.